/*******************************************************************************
 Chatty - Mobile Messaging - J2ME ICQ clone
 Copyright (C) 2003-05  Chatty Project

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 ********************************************************************************
 File: src/DrawControls/VirtualList.java
 Version: ###VERSION###  Date: ###DATE###
 Author(s): Artyomov Denis, Igor Palkin, Vladimir Kryukov
 *******************************************************************************/

package DrawControls;
import DrawControls.icons.Icon;
import javax.microedition.lcdui.*;

import chatty.Options;
import chatty.comm.StringConvertor;
import chatty.modules.MagicEye;
import chatty.ui.*;
import chatty.ui.base.*;

/**
 * This class is base class of owner draw list controls
 *
 * It allows you to create list with different colors and images. 
 * Base class of VirtualDrawList if Canvas, so it draw itself when
 * paint event is heppen. VirtualList have cursor controlled of
 * user
 */

public abstract class VirtualList extends CanvasEx {
    public final static int MEDIUM_FONT = Font.SIZE_MEDIUM;
    public final static int LARGE_FONT = Font.SIZE_LARGE;
    public final static int SMALL_FONT = Font.SIZE_SMALL;
    
    // Commands to react to VL events
    private VirtualListCommands vlCommands;

    // Caption of VL
    private Icon[] capImages;
    private String caption;
    private static Icon messageIcon;
	public  Icon menuIcon;
    private String ticker;
    private static int captionHeight = -1;

    // Index for current item of VL
    private int currItem = 0;
    // Index of top visilbe item 
    private int topItem = 0;
    private int topOffset = 0;

    // Set of fonts for quick selecting
    private Font[] fontSet;

    protected static final byte MP_ALL = 0;
    protected static final byte MP_SELECTABLE_OLNY =1;
    private byte movingPolicy = MP_ALL;


    //! Create new virtual list with default values  
    public VirtualList(String capt) {
        setCaption(capt);
        fontSet = GraphicsEx.chatFontSet;
    }
    public static final void setMessageIcon(Icon icon) {
        messageIcon = icon;
    }
    public static final Icon getMessageIcon() {
        return messageIcon;
    }
	public final void setMenuIcon(Icon icon) {
        menuIcon = icon;
    }
    protected final void setMovingPolicy(byte mp) {
        movingPolicy = mp;
    }

    /**
     * Request number of list elements to be shown in list.
     *
     * You must return number of list elements in successtor of
     * VirtualList. Class calls method "getSize" each time before it drawn
     */
    abstract protected int getSize();

    protected final Font[] getFontSet() {
        return fontSet;
    }

    public final Font getDefaultFont() {
        return fontSet[FONT_STYLE_PLAIN];
    }
    
    // returns height of draw area in pixels
    protected final int getContentHeight() {
        return getHeight() - getCapHeight() - 1;
    }

    //! Sets new font size and invalidates items
    protected final void setFontSet(Font[] set) {
        fontSet = set;
    }

    public final void setCapImages(Icon[] images) {
        capImages = images;
        invalidate();
    }
    
    public final void setVLCommands(VirtualListCommands vlCommands) {
        this.vlCommands = vlCommands;
    }
    
    /** Returns height of each item in list */
    protected abstract int getItemHeight(int itemIndex);

    protected final void setCurrentItemIndex(int index) {
        currItem = index;
        setOptimalTopItem(topItem, currItem);
    }
    protected final void setCurrentItemToTop(int index) {
        currItem = index;
        setTopOnPoints(getFullSize() - getContentHeight());
        setOptimalTopItem(topItem, currItem);
        if (topItem == currItem) {
            topOffset = 0;
        }
    }

    private void setOptimalTopItem(int top, int currItem) {
        int size = getSize();
        if (0 == size) {
            topItem = 0;
            topOffset = 0;
            return;
        }
        if (currItem <= top) {
            topItem = Math.max(0, currItem);
            topOffset = Math.max(0, getItemHeight(topItem) - getContentHeight());
            return;

        } else {
            top = Math.min(top, size - 1);
            int height = 0;
            int offset = getContentHeight();
            for (int item = currItem; top <= item; --item) {
                height = getItemHeight(item);
                offset -= height;
                if (offset <= 0) {
                    offset = -offset;
                    if (item == currItem) {
                        offset = 0;
                    }
                    if (topItem > item) {
                    } else if ((topItem == item) && (topOffset > offset)) {
                    } else {
                        topItem = item;
                        topOffset = offset;
                    }
                    return;
                }
            }
        }
    }

    protected void onCursorMove() {
    }

    private final void ___setCurrentItem(int index) {
        index = Math.max(0, Math.min(index, getSize() - 1));
        int lastCurrItem = currItem;
        if (lastCurrItem != index) {
            currItem = index;
            onCursorMove();
            invalidate();
        }
    }
    public final void setCurrentItem(int index) {
        index = Math.max(0, Math.min(index, getSize() - 1));
        int lastCurrItem = currItem;
        if (lastCurrItem != index) {
            setCurrentItemIndex(index);
            onCursorMove();
            invalidate();
        }
    }
    private final void setCurrentOnPoint(int offset) {
        int size = getSize();
        for (int i = 0; i < size; ++i) {
            int height = getItemHeight(i);
            if (offset < height) {
                setCurrentItem(i);
                return;
            }
            offset -= height;
        }
    }

    public final int getCurrItem() {
        return currItem;
    }

    protected boolean isItemSelectable(int index) {
        return true;
    }
    protected final void setTopOnPoints(int offset) {
        int size = getSize();
        offset = Math.max(0, Math.min(offset, getFullSize() - getContentHeight() + 1));
        for (int i = 0; i < size; ++i) {
            int height = getItemHeight(i);
            if (offset < height) {
                if ((topItem != i) || (topOffset != offset)) {
                    topItem = i;
                    topOffset = offset;
                    invalidate();
                }
                return;
            }
            offset -= height;
        }
    }
    private final void moveCursor(int step) {
        int top     = getTopOffset();
        int visible = getContentHeight();
        
		TouchControl nat = NativeCanvas.getInstance().touchControl;
        if (nat.touchUsed) {
            nat.touchUsed = false;
            int current = getOffset(currItem);
            if ((current + getItemHeight(currItem) < top) || (top + visible < current)) {
                if (step < 0) {
                    setCurrentOnPoint(top + visible - 1);
                } else {
                    setCurrentOnPoint(top + 1);
                }
                return;
            }
        }

        int next = getCurrItem() + step;
        if (MP_SELECTABLE_OLNY == movingPolicy) {
            while (!isItemSelectable(next)) {
                next += step;
                if ((next < 0) || (getSize() <= next)) {
                    break;
                }
            }
        }
        next = Math.max(-1, Math.min(next, getSize()));
        if (0 < step) {
            if (getSize() == next) {
                int end = getFullSize() - visible;
                if (top < end) {
                    setTopOnPoints(Math.min(end, top + visible / 3));
                    return;
                }
            } else {
                int nextOffset = getOffset(next);
                if (top + visible < nextOffset) {
                    setTopOnPoints(top + visible / 3);
                    return;
                }
            }
        } else {
            if (-1 == next) {
                if (0 < top) {
                    setTopOnPoints(Math.max(0, top - visible / 3));
                    return;
                }
            } else {
                if (getOffset(next) + getItemHeight(next) < top) {
                    setTopOnPoints(top - visible / 3);
                    return;
                }
            }
        }
        if ((next < 0) || (getSize() <= next)) {
            return;
        }
        setCurrentItem(next);
    }

    protected void itemSelected() {}
    
    private void navigationKeyReaction(int keyCode, int actionCode) {
        switch (actionCode) {
            case NativeCanvas.NAVIKEY_DOWN:
                moveCursor(+1);
                break;
            case NativeCanvas.NAVIKEY_UP:
                moveCursor(-1);
                break;
            case NativeCanvas.NAVIKEY_FIRE:
                itemSelected();
                break;
        }
        switch (keyCode) {
        case NativeCanvas.KEY_NUM1:
            setTopOnPoints(0);
            if (currItem != 0) {
                currItem = 0;
                invalidate();
            }
            break;
            
        case NativeCanvas.KEY_NUM7:
            setTopOnPoints(getFullSize() - getContentHeight());
            if (currItem != getSize() - 1) {
                currItem = getSize() - 1;
                invalidate();
            }
            break;

        case NativeCanvas.KEY_NUM3:
            int top = getTopVisibleItem();
            if (currItem == top) {
                setTopOnPoints(getTopOffset() - getContentHeight() * 9 / 10);
                top = getTopVisibleItem();
            }
            if (currItem != top) {
                currItem = top;
                invalidate();
            }
            break;
            
        case NativeCanvas.KEY_NUM9:
            int bottom = getBottomVisibleItem();
            if (currItem == bottom) {
                setTopOnPoints(getTopOffset() + getContentHeight() * 9 / 10);
                bottom = getBottomVisibleItem();
            }
            if (currItem != bottom) {
                currItem = bottom;
                invalidate();
            }
            break;
        }
    }
    private int getTopVisibleItem() {
        int size = getSize();
        int cur = topItem;
        if ((cur + 1 < size) && (0 < topOffset)) {
            int used = getItemHeight(cur) - topOffset;
            int height = getContentHeight() - used;
            if ((getItemHeight(cur + 1) < height) || (used < 5)) {
                cur++;
            }
        }
        return cur;
    }
    private int getBottomVisibleItem() {
        int size = getSize();
        int cur = size;
        int offset = getContentHeight() + topOffset;
        for (int i = topItem; i < size; ++i) {
            int height = getItemHeight(i);
            if (offset < height) {
                cur = i;
                break;
            }
            offset -= height;
        }
        offset = Math.max(0, offset);
        cur = (size == cur) ? size - 1 : Math.max(topItem, cur - 1);
        return cur;
    }

    protected void doKeyReaction(int keyCode, int actionCode, int type) {
        if ((null != vlCommands) && (KEY_PRESSED == type)) {
            if (vlCommands.onKeyPress(this, keyCode, actionCode)) {
                return;
            }
        }
        if ((KEY_REPEATED == type) || (KEY_PRESSED == type)) {
            navigationKeyReaction(keyCode, actionCode);
        }
    }

    protected int getItemByCoord(int x, int y) {
        // is pointing on scroller
        if (x >= (getWidth() - 3 * scrollerWidth)) {
            return -1;
        }
        
        int size = getSize();
        // is pointing on data area
        int itemY1 = getCapHeight() - topOffset;
        if (y < itemY1) {
            for (int i = topItem; 0 <= i; --i) {
                if (itemY1 <= y) {
                    return i;
                }
                itemY1 -= getItemHeight(i);
            }
            
        } else {
            for (int i = topItem; i < size; ++i) {
                itemY1 += getItemHeight(i);
                if (y < itemY1) {
                    return i;
                }
            }
        }
        return -1;
    }

    protected boolean pointerPressed(int item, int x, int y) {
        final int currentItem = getCurrItem();
        if (currentItem != item) {
            ___setCurrentItem(item);
            return true;
        }
        return false;
    }
    
    protected void stylusGeneralYMoved(int fromX, int fromY, int toX, int toY) {
        if (fromX >= (getWidth() - 2 * scrollerWidth)) {
            TouchControl nat = NativeCanvas.getInstance().touchControl;
            setTopOnPoints(nat.calcScrollPosition(getFullSize()));

        } else {
            int item = getItemByCoord(toX, toY);
            if (0 <= item) {
                TouchControl nat = NativeCanvas.getInstance().touchControl;
                setTopOnPoints(nat.prevTopY + (fromY - toY));
            }
        }
    }

    protected final void stylusPressed(int x, int y) {
        TouchControl nat = NativeCanvas.getInstance().touchControl;
        if (y < getCapHeight()) {
            nat.scrollingOn = false;
            return;
        }
        nat.touchUsed = true;

        if (x >= (getWidth() - 2 * scrollerWidth)) {
            y -= getCapHeight();
            int[] metrix = GraphicsEx.getVertScrollMetrix(getContentHeight() + 1,
                    getTopOffset(), getContentHeight(), getFullSize());
            
            nat.tappingOn = false;
            nat.scrollingOn = false;
            nat.kineticOn = false;
            if (null == metrix) {
                return;
            }
            int scrollHeight = (metrix[1] - metrix[0]);
            int scrollMiddle = (metrix[1] + metrix[0]) / 2;
            int scrollLikeHeight = Math.max(scrollHeight, minItemHeight);
            int someY = y - (scrollMiddle - scrollLikeHeight / 2);
            if ((0 < someY) && (someY < scrollLikeHeight)) {
                nat.scrollingOn = true;
                nat.prevTopY = metrix[0];
                nat.prevHeight = getContentHeight();
            }

        } else {
            int item = getItemByCoord(x, y);
            if (0 <= item) {
                nat.prevTopY = getTopOffset();
                nat.prevHeight = getItemHeight(item);
                nat.isSecondTap = !pointerPressed(item, x, y);
            }
        }
    }

    protected void itemTap(int item, int x) {
    }
    protected void captionTapped() {
    }
    
    protected void stylusTap(int x, int y, boolean longTap) {
        if (x >= (getWidth() - scrollerWidth)) {
            return;
        }
        if (y < getCapHeight()) {
            captionTapped();
            return;
        }
        int item = getItemByCoord(x, y);
        if (item >= 0) {
            if (longTap || NativeCanvas.getInstance().touchControl.isSecondTap) {
                itemSelected();
                itemTap(item, x);
            }
        }
    }

    /**
     * Set caption text for list
     */
    public final void setCaption(String capt) {
        if ((null == caption) || !caption.equals(capt)) {
            caption = capt;
        }
    }

    public final String getCaption() {
        return caption;
    }
    
    public final void setTicker(String tickerString) {
        if ((null != tickerString) && tickerString.equals(ticker)) {
            return;
        }
        ticker = tickerString;
        if (NativeCanvas.isFullScreen()) {
            invalidate();
        }
    }
    
    protected int getCapHeight() {
        captionHeight = Math.max(captionHeight, GraphicsEx.calcCaptionHeight(capImages, caption));
        return captionHeight;
    }

    protected boolean isItemSelected(int index) {
        return (currItem == index);
    }

    protected void drawProgress(GraphicsEx g, int width, int height) {
    }
    protected void drawToolBar(GraphicsEx g) {
	}
    private int getOffset(int max) {
        int height = 0;
        for (int i = 0; i < max; ++i) {
            height += getItemHeight(i);
        }
        return height;
    }
    protected final int getTopOffset() {
        return getOffset(topItem) + topOffset;
    }
    protected final int getFullSize() {
        return getOffset(getSize());
    }
    
    protected void beforePaint() {
    }
    protected void paint(GraphicsEx g) {
        beforePaint();

        int captionHeight = getCapHeight();
		
		drawItems(g, captionHeight);
		
        g.setStrokeStyle(Graphics.SOLID);
        g.setClip(0, 0, getWidth(), captionHeight + 1);
        g.drawBarBack(0, captionHeight/*, Scheme.captionImage*/);
        drawProgress(g, getWidth(), captionHeight);
        g.drawCaption(capImages, (null == ticker) ? caption : ticker, messageIcon, menuIcon, captionHeight);
        
        g.setClip(getWidth() - scrollerWidth, captionHeight,
                scrollerWidth, getHeight() - captionHeight);
        g.drawVertScroll(getWidth() - scrollerWidth, captionHeight,
                scrollerWidth, getHeight() - captionHeight,
                getTopOffset(), getHeight() - captionHeight, getFullSize(),
                /*THEME_BACKGROUND,*/ THEME_SCROLL_BACK);
		if (Options.getBoolean(Options.OPTION_TOOLBAR)) {
			if (NativeCanvas.getInstance().hasPointerEvents() && chatty.Chatty.icons != null)
				drawToolBar(g);	
		}
    }

    private void drawItems(GraphicsEx g, int top_y) {
        int height = getHeight();
        int size = getSize();
        int itemWidth = getWidth() - scrollerWidth;
        
        // Fill background
        g.setThemeColor(THEME_BACKGROUND);
        g.fillRect(0, top_y, itemWidth, height - top_y);

        //g.setClip(0, top_y, itemWidth, height - top_y);
        //if (null != Skin.getSkin()) {
        //    g.drawImage(Skin.getSkin(), itemWidth / 2, (height + top_y) / 2,
        //           Graphics.HCENTER | Graphics.VCENTER);
        //}
        
        // Draw items
        int offset = topOffset;
        int y = top_y;

        boolean showCursor = false;
        int currentY = 0;
        int currentIndex = isItemSelected(getCurrItem()) ? getCurrItem() : -1;
        
        for (int i = topItem; i < size; ++i) {
            int itemHeight = getItemHeight(i);
            int realHeight = Math.min(itemHeight, height - y + 1);
            g.setClip(0, y, itemWidth, realHeight + 1);
            g.setStrokeStyle(Graphics.SOLID);
            if (i == currentIndex) {
                currentY = y - offset;
                if (Scheme.fillCursor) {
                    g.setThemeColor(THEME_SELECTION_BACK);
                    g.fillRect(0, currentY, itemWidth - 1, itemHeight);
                }
                showCursor = true;
            }
            drawItemData(g, i, 2, y - offset, itemWidth - 4, itemHeight, offset, realHeight);
            y += itemHeight - offset;
            if (y >= height) break;
            offset = 0;
        }
        if (showCursor) {
            y = currentY;
            int itemHeight = getItemHeight(currentIndex);
            g.setClip(0, y, itemWidth, itemHeight + 1);
            g.setThemeColor(THEME_SELECTION_RECT);
            g.setStrokeStyle(Scheme.fillCursor ? Graphics.SOLID : Graphics.DOTTED);
            //g.drawSimpleRect(0, y, itemWidth - 1, itemHeight);
            g.drawLine(0, y, itemWidth, y);
            g.drawLine(0, y + itemHeight, itemWidth, y + itemHeight);
        }
    }

    protected abstract void drawItemData(GraphicsEx g, int index, int x1, int y1, int w, int h, int skip, int to);
    
    protected int getHeight() {
        return getScreenHeight();
    }
    
    protected int getWidth() {
        return getScreenWidth();
    }
}